/*******************************************************************************
 * Copyright (c) 2004, 2009 MAKE Technologies Inc and others
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *   MAKE Technologies Inc - initial API and implementation
 *   Mariot Chauvin <mariot.chauvin@obeo.fr> - refactoring
 *******************************************************************************/
package org.eclipse.swtbot.eclipse.gef.finder.widgets;

import java.lang.reflect.Method;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable;
import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException;
import org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory;
import org.eclipse.swtbot.swt.finder.results.VoidResult;
import org.eclipse.swtbot.swt.finder.utils.MessageFormat;
import org.eclipse.swtbot.swt.finder.utils.internal.Assert;
import org.eclipse.swtbot.swt.finder.widgets.AbstractSWTBot;
import org.hamcrest.Matcher;

/**
 * a context menu implementation that finds the menu and invokes it in one UIRunnable so as to avoid the
 * disposed-before-clicked issue that occurs within Eclipse.
 * 
 * @author David Green
 *
 */
public class SWTBotGefContextMenu extends AbstractSWTBot<Control>{
	private final String text;
	private final Control control;

	public SWTBotGefContextMenu(final Control control, final String text) {
		super(control);
		this.text = text;
		this.control = control;
	}
	
	public SWTBotGefContextMenu click() throws WidgetNotFoundException {
		final boolean[] clicked = new boolean[1];
		// must async exec in case the call opens a dialog
		UIThreadRunnable.asyncExec(this.display, new VoidResult() {
			public void run() {
				Menu menu = control.getMenu();
				invokeMenuInternal(menu, WidgetMatcherFactory.withMnemonic(text),clicked, true);
			}
		});
		// use another sync exec to ensure that previous call was processed fully
		UIThreadRunnable.syncExec(this.display,new VoidResult() {
			public void run() {
				// do nothing, just wait for all events to be processed 
			}
		});
		if (!clicked[0]) {
			throw new WidgetNotFoundException(text);
		}
		return this;
	}
	

	private boolean invokeMenuInternal(final Menu bar, final Matcher<? extends Widget> matcher,final boolean[] clickInitiated, final boolean recursive) {
		if (bar != null) {
			bar.notifyListeners(SWT.Show, new Event());
			try {
				MenuItem[] items = bar.getItems();
				for (int i = 0; i < items.length; i++) {
					MenuItem menuItem = items[i];
					if (isSeparator(menuItem)) {
						continue;
					}
					if (matcher.matches(menuItem)) {
						clickInitiated[0] = true;
						click(menuItem);
						return true;
					}
					if (recursive) {
						if (invokeMenuInternal(menuItem.getMenu(), matcher,clickInitiated, recursive)) {
							return true;
						}
					}
				}
			} finally {
				bar.notifyListeners(SWT.Hide, new Event());
			}
		}
		return false;
	}
	
	private void click(MenuItem menuItem) {
		assertEnabled(menuItem);
		int style = menuItem.getStyle();
		if (hasStyle(style, SWT.CHECK) | hasStyle(style, SWT.RADIO)) {
			menuItem.setSelection(!menuItem.getSelection());
		}
		Event event = new Event();
		event.time = (int) System.currentTimeMillis();
		event.widget = menuItem;
		event.display = menuItem.getDisplay();
		menuItem.notifyListeners(SWT.Selection,event);
	}

	private boolean hasStyle(int style, int flag) {
		return (style & flag) == flag;
	}

	private boolean isSeparator(MenuItem menuItem) {
		// FIXME see https://bugs.eclipse.org/bugs/show_bug.cgi?id=208188
		// FIXED > 20071101 https://bugs.eclipse.org/bugs/show_bug.cgi?id=208188#c2
		return (menuItem.getStyle() & SWT.SEPARATOR) != 0;
	}
	
	
	 private void assertEnabled(MenuItem menuItem) {
	        Assert.isTrue(isEnabled(menuItem), MessageFormat.format("Widget {0} is not enabled.", this)); //$NON-NLS-1$ //$NON-NLS-2$
	 }
	
	
	protected boolean isEnabled(Widget widget) {
		try {
			Method method = widget.getClass().getMethod("isEnabled");
			return (Boolean) method.invoke(widget);
		} catch (Exception e) {
			return false;
		}
	}
}
